package com.xishu.aop;

import com.xishu.annotation.valid.Admin;
import com.xishu.annotation.valid.MethodValidator;
import com.xishu.config.Config;
import com.xishu.config.Constant;
import com.xishu.entity.AbstractEntity;
import com.xishu.entity.User;
import com.xishu.entity.plat.UserCompanyMapping;
import com.xishu.entity.plat.UserShopMapping;
import com.xishu.entity.shop.Shop;
import com.xishu.exception.ResponseStatusException;
import com.xishu.response.ResponseStatus;
import com.xishu.response.VerifyUtil;
import com.xishu.service.*;
import com.xishu.util.ClassUtil;
import com.xishu.util.JRedisUtil;
import com.xishu.util.ServletUtil;
import com.xishu.util.Tools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;

import static com.xishu.config.Constant.*;
import static com.xishu.response.ResponseStatus.CONFIG_WRONG;
import static com.xishu.response.ResponseStatus.USER_NOT_LOGIN;

public class Validator implements Tools {
    private static Logger logger = LoggerFactory.getLogger(Validator.class);

    public void checkAdminUser(Method method) throws Exception {
        if (isAuditMethod(method)) {
            User user = ServletUtil.getUser();
            VerifyUtil.verify(() -> user != null, USER_NOT_LOGIN);

            //如果不合法，直接抛出异常
            if (!getBoolean(user.getAdmin())) {
                VerifyUtil.throwError(ResponseStatus.ERROR_USER);
            }
        }
    }

    /**
     * 检查是否是后台的方法
     *
     * @returnsse'r
     */
    public boolean isAuditMethod(Method method) {
        Admin annotation = method.getAnnotation(Admin.class);
        return annotation != null;
    }


    /**
     * 检测方法的调用
     *
     * @param method
     * @param o
     * @throws Exception
     */
    public void checkMethodValidator(Method method, Object o) throws Exception {
        long startTime = System.currentTimeMillis();
        User user = ServletUtil.getUser();
        checkMustLogin(method, user);

        checkCompany(method, o, user);
        checkShop(method, o, user);
        checkCompanyUser(method, o, user);
        checkPlatManagerUser(method, o, user);
        checkCustomerLogin(method, user);

        logger.info("cost time {} in checkMethodValidator", System.currentTimeMillis() - startTime);
    }

    public void checkMustLogin(Method method, User user) throws ResponseStatusException {
        if (isLoginMethod(method)) {
            if (user == null) {
                VerifyUtil.throwError(USER_NOT_LOGIN);
            }

            VerifyUtil.verify(() -> getInt(user.getStatus()) == Constant.USER_STATUS_OK, ResponseStatus.USER_NOT_PASS);

            //必须要登录的账号才判断
            checkUserSessionValid();
        }
    }


    public void checkCustomerLogin(Method method, User user) throws ResponseStatusException {
        if (isCustomerLoginMethod(method)) {
            if (ServletUtil.getUserId() != null) {
                logger.info("system user");
                return;
            }

            Long customerId = ServletUtil.getCustomerId();

            VerifyUtil.verify(() -> customerId != null, ResponseStatus.CUSTOMER_NOT_LOGIN);
        }
    }

    /**
     * 检查用户的会话是否还有效
     */
    public void checkUserSessionValid() throws ResponseStatusException {
        Long companyId = ServletUtil.getSessionAttribute(SESSION_COMPANY_ID);
        //没有该标记，不用处理
        if (companyId == null) {
            return;
        }

        logger.info("company id is {}", companyId);

        //获取公司的权限修改时间
        String kv = JRedisUtil.getKV(COMPANY_PRIVILEGE_UPDATE_TIME + companyId);
        if (kv == null) {
            return;
        }

        Long userUpdateTime = Long.valueOf(kv);

        //最近一个小时没有更新，那么不用处理
        if (userUpdateTime == null) {
            return;
        }

        logger.info("update time is {}", userUpdateTime);

        Long loginTime = ServletUtil.getSessionAttribute(USER_SESSION_LOGIN_TIME);
        if (loginTime == null) {
            return;
        }

        logger.info("user login time is {}", loginTime);

        Boolean bEndLogin = ServletUtil.getSessionAttribute(SESSION_B_END_LOGIN);
        if (bEndLogin != null) {
            //B端登录的账号，不用关心权限的修改
            return;
        }

        //登录的时间大于更新时间才算是合法的会话
        boolean valid = loginTime > userUpdateTime;

        VerifyUtil.verify(() -> valid, ResponseStatus.USER_NOT_LOGIN);
    }

    /**
     * 是否要检查公司
     *
     * @param method
     * @return
     */
    public boolean isCompanyMethod(Method method) {
        MethodValidator methodValidator = method.getAnnotation(MethodValidator.class);
        return methodValidator != null && methodValidator.checkCompany();
    }

    /**
     * 是否要检查分店
     *
     * @param method
     * @return
     */
    public boolean isShopMethod(Method method) {
        MethodValidator methodValidator = method.getAnnotation(MethodValidator.class);
        return methodValidator != null && methodValidator.checkShop();
    }

    /**
     * 该方法是否要做账号级别检测
     *
     * @param method
     * @return
     */
    public boolean isCheckCompanyUserMethod(Method method) {
        MethodValidator methodValidator = method.getAnnotation(MethodValidator.class);
        return methodValidator != null && methodValidator.companyUser();
    }

    /**
     * 该方法是否要做账号级别检测
     *
     * @param method
     * @return
     */
    public boolean isPlatManagerUserMethod(Method method) {
        MethodValidator methodValidator = method.getAnnotation(MethodValidator.class);
        return methodValidator != null && methodValidator.platManagerUser();
    }

    /**
     * 检查是否是餐厅公司的管理员
     *
     * @param method
     * @param o
     * @param user
     * @throws Exception
     */
    public void checkCompanyUser(Method method, Object o, User user) throws Exception {
        if (isCheckCompanyUserMethod(method)) {
            VerifyUtil.verify(() -> isCompanyManager(user), ResponseStatus.PRIVILEGE);
        }
    }

    /**
     * 判断是否具有平台管理员的权限
     *
     * @param user
     * @return
     */
    public boolean isPlatManager(User user) {
        if (user == null) {
            return false;
        }

        return getBoolean(user.getAdmin()) || getBoolean(user.getPlatManager());
    }

    /**
     * 检查是否是平台管理员权限
     *
     * @param method
     * @param o
     * @param user
     * @throws ResponseStatusException
     */
    public void checkPlatManagerUser(Method method, Object o, User user) throws ResponseStatusException {
        if (isPlatManagerUserMethod(method)) {
            VerifyUtil.verify(() -> isPlatManager(user), ResponseStatus.PRIVILEGE);
        }
    }

    /**
     * 检查是否有公司的权限
     *
     * @param method
     */
    public void checkCompany(Method method, Object o, User user) throws Exception {
        //用户为空的方法不用处理，要校验公司的方法一定是已经登录的
        if (user == null) {
            return;
        }

        if (isCompanyMethod(method)) {
            //超级管理员直接返回
            if (getBoolean(user.getAdmin())) {
                return;
            }

            boolean boolInstance = o instanceof AbstractEntity;

            //不是统一的类型不处理
            if (!boolInstance) {
                return;
            }

            Field companyIdField = ClassUtil.getInstance().getField(o.getClass(), "companyId");
            companyIdField.setAccessible(true);

            //必须要有companyId这个字段
            if (companyIdField == null) {
                VerifyUtil.throwError(ResponseStatus.SYSTEM_ERROR);
                return;
            }

            //直接根据companyId来操作
            MethodValidator methodValidator = method.getAnnotation(MethodValidator.class);
            if (methodValidator.checkCompanyId()) {
                VerifyUtil.verify(() -> {
                    try {
                        companyIdField.setAccessible(true);
                        Long companyId = (Long) companyIdField.get(o);
                        MappingService mappingService = MappingService.getInstance();
                        UserCompanyMapping userCompanyMapping = mappingService.findUserCompanyMapping(user.id, companyId);

                        return userCompanyMapping != null;

                    } catch (Exception e) {
                        logger.error("e", e);
                        return false;
                    }
                }, ResponseStatus.PRIVILEGE);
                return;
            }

            AbstractEntity newInstance = (AbstractEntity) o.getClass().newInstance();
            AbstractEntity o1 = (AbstractEntity) o;

            if (isNotEmpty(o1.getId())) {
                newInstance.setId(o1.getId());

                AbstractEntity instanceInDb = CommonService.getInstance().findEntity(newInstance);

                Object companyId = companyIdField.get(instanceInDb);

                if (companyId == null) {
                    VerifyUtil.throwError(ResponseStatus.SYSTEM_ERROR);
                    return;
                }

                MappingService mappingService = MappingService.getInstance();

                try {
                    UserCompanyMapping userCompanyMapping = mappingService.findUserCompanyMapping(user.id, Long.valueOf(companyId.toString()));
                    VerifyUtil.verify(() -> userCompanyMapping != null, ResponseStatus.PRIVILEGE);
                } catch (Exception e) {
                    logger.error("e", e);
                    VerifyUtil.throwError(ResponseStatus.PRIVILEGE);
                }

            }
        }
    }

    /**
     * 检查是否有公司的权限
     *
     * @param method
     */
    public void checkShop(Method method, Object o, User user) throws Exception {
        //用户为空的方法不用处理，要校验公司的方法一定是已经登录的
        if (user == null) {
            return;
        }

        if (isShopMethod(method)) {
            //超级管理员直接返回
            if (getBoolean(user.getAdmin())) {
                return;
            }

            MethodValidator methodValidator = method.getAnnotation(MethodValidator.class);
            Field shopIdField = ClassUtil.getInstance().getField(o.getClass(), "shopId");
            shopIdField.setAccessible(true);

            //直接根据shopId来操作
            if (methodValidator.checkShopId()) {
                VerifyUtil.verify(() -> {
                    try {
                        Long shopId = (Long) shopIdField.get(o);
                        return UserService.getInstance().hasShopPrivilege(user, shopId);
                    } catch (Exception e) {
                        logger.error("e", e);
                        return false;
                    }
                }, ResponseStatus.PRIVILEGE);
                return;
            }


            boolean boolInstance = o instanceof AbstractEntity;

            //不是统一的类型不处理
            if (!boolInstance) {
                return;
            }


            //必须要有companyId这个字段
            if (shopIdField == null) {
                VerifyUtil.throwError(ResponseStatus.SYSTEM_ERROR);
                return;
            }

            AbstractEntity newInstance = (AbstractEntity) o.getClass().newInstance();
            AbstractEntity operateObject = (AbstractEntity) o;

            if (isNotEmpty(operateObject.getId())) {
                newInstance.setId(operateObject.getId());

                AbstractEntity instanceInDb = CommonService.getInstance().findEntity(newInstance);

                Object shopId = shopIdField.get(instanceInDb);

                if (shopId == null) {
                    VerifyUtil.throwError(ResponseStatus.SYSTEM_ERROR);
                    return;
                }

                UserService.getInstance().hasShopPrivilege(user, Long.valueOf(shopId.toString()));
            }
        }
    }

    /**
     * 判断用户是否对该分店有操作的权限
     *
     * @param userId
     * @param shopId
     */
    public void validUser(Long userId, Long shopId) throws Exception {
        UserShopMapping userShopMapping = MappingService.getInstance().findUserShopMapping(userId, shopId);
        VerifyUtil.verify(() -> userShopMapping != null, ResponseStatus.PRIVILEGE);
        //至少要是餐厅经理的级别
        User user = UserService.getInstance().findUser(userId);
        VerifyUtil.verify(() -> isShopManager(user), ResponseStatus.PRIVILEGE);
    }

    /**
     * 是否必须要登录
     *
     * @param method
     */
    public boolean isLoginMethod(Method method) {
        MethodValidator methodValidator = method.getAnnotation(MethodValidator.class);
        return methodValidator == null || methodValidator.mustLogin();
    }

    /**
     * 是否要求会员登录
     *
     * @param method
     * @return
     */
    public boolean isCustomerLoginMethod(Method method) {
        MethodValidator methodValidator = method.getAnnotation(MethodValidator.class);
        return methodValidator == null || methodValidator.requireCustomerLogin();
    }

    /**
     * 判断当前账号是否是企业的管理员权限
     *
     * @return
     */
    public boolean isCompanyManager() {
        User user = null;

        try {
            user = ServletUtil.getUser();
        } catch (Exception e) {
            logger.error("e", e);
            return false;
        }

        return isCompanyManager(user);
    }

    /**
     * 判断当前账号是否是企业的管理员权限
     *
     * @return
     */
    public boolean isCompanyManager(User user) {
        if (user == null) {
            return false;
        }

        return getBoolean(user.getAdmin()) || getBoolean(user.getPlatManager()) || getBoolean(user.getCompanyManager());
    }

    /**
     * 判断用户是否是餐厅经理的权限
     *
     * @param user
     * @return
     */
    public boolean isShopManager(User user) {
        if (user == null) {
            return false;
        }

        return getBoolean(user.getAdmin()) || getBoolean(user.getPlatManager()) || getBoolean(user.getCompanyManager()) || getBoolean(user.getShopManager());
    }

    public boolean companyRelated(Long companyId) {
        try {
            User user = ServletUtil.getUser();
            return companyRelated(companyId, user);
        } catch (Exception e) {
            logger.error("e", e);
            return false;
        }
    }

    /**
     * 该账号是否与该公司相关
     *
     * @param companyId
     * @param user
     * @return
     */
    public boolean companyRelated(Long companyId, User user) {
        Long userId = user.getId();
        if (companyId == null) {
            logger.info("company id null");
            return false;
        }

        if (userId == null) {
            logger.info("user id null");
            return false;
        }

        if (getBoolean(user.getAdmin())) {
            return true;
        }

        UserCompanyMapping userCompanyMapping = new UserCompanyMapping();
        userCompanyMapping.setCompanyId(companyId);
        userCompanyMapping.setUserId(userId);
        logger.info("user id {}, company id {}", userId, companyId);
        List<UserCompanyMapping> mappingList = CommonService.getInstance().searchList(userCompanyMapping);
        if (isNotEmpty(mappingList)) {
            logger.info("user has the company mapping");
            return true;
        }

        //如果是平台管理员或者是餐厅管理员，没有映射的餐厅，则直接结束
        if (getBoolean(user.getPlatManager()) || getBoolean(user.getCompanyManager())) {
            logger.info("plat user or company user do not has the mapping");
            return false;
        }

        //还不是分店管理员，则直接退出
        if (!getBoolean(user.getShopManager())) {
            logger.info("not the shop manager");
            return false;
        }

        UserShopMapping userShopMapping = new UserShopMapping();
        userShopMapping.setUserId(userId);
        List<UserShopMapping> userShopMappingList = CommonService.getInstance().searchList(userShopMapping);
        if (isEmpty(userShopMappingList)) {
            logger.info("do not has the user shop mapping");
            return false;
        }

        for (UserShopMapping shopMapping : userShopMappingList) {
            try {
                Long shopId = shopMapping.getShopId();
                Shop shop = ShopService.getInstance().findShopById(shopId);
                if (equals(shop.getCompanyId(), companyId)) {
                    logger.info("find the shop {}", shopId);
                    return true;
                }
            } catch (Exception e) {
                logger.error("e", e);
            }
        }

        logger.info("do not find the property shop");
        return false;
    }

    /**
     * 同一个会话里面，相同的请求不应该同时发生，如果同时发生了，那么忽略掉其中的一些
     * <p>
     * 返回true表示正常，返回false表示异常，需要忽略掉
     *
     * @param method
     * @param sessionId
     * @return
     */
    public boolean duplicationRequestMethodCheck(Method method, String sessionId, String requestUrl) {
        MethodValidator methodValidator = method.getAnnotation(MethodValidator.class);
        if (methodValidator == null) {
            return true;
        }

        boolean duplicationRequestCheck = methodValidator.duplicationRequestCheck();

        //不需要检查
        if (!duplicationRequestCheck) {
            return true;
        }

        return duplicationCheck(sessionId, requestUrl);
    }

    /**
     * 重复类检查
     *
     * @return
     */
    public boolean duplicationCheck() {
        HttpServletRequest request = ServletUtil.getRequest();
        return duplicationCheck(ServletUtil.getSessionId(), request.getRequestURI());
    }

    private boolean duplicationCheck(String sessionId, String requestUrl) {
        String lockKey = sessionId + SEPERATOR_KEY + requestUrl;
        //开始检查
        Object lock = LockService.getInstance().getLock(lockKey);

        synchronized (lock) {
            //从缓存里面读取，没有写在JAVA内存里面，以防止FULL GC
            String visitTimeString = JRedisUtil.getKV(lockKey);

            //没有访问过
            if (visitTimeString == null) {
                JRedisUtil.setKV(lockKey, String.valueOf(System.currentTimeMillis()));
                return true;
            }

            //如果存在过，看下时间
            Long visitTime = Long.valueOf(visitTimeString);
            if (System.currentTimeMillis() - visitTime < Config.getInstance().duplicationRequestTime()) {
                logger.info("time error, duplication request");
                return false;
            }

            //超过有效的时间范围，写入标记
            JRedisUtil.setKV(lockKey, String.valueOf(System.currentTimeMillis()));
            return true;
        }
    }

    /**
     * 判断KEY是否有过期
     * 如果写过，或者超过了过期时间都算过期
     * 如果在指定的时间范围内还存在，说明KEY没有过期
     *
     * @param key
     * @param expireTime
     * @return
     */
    public boolean keyExpire(String key, long expireTime) {
        synchronized (key) {
            //从缓存里面读取，没有写在JAVA内存里面，以防止FULL GC
            String visitTimeString = JRedisUtil.getKV(key);

            //没有访问过
            if (visitTimeString == null) {
                JRedisUtil.setKV(key, String.valueOf(System.currentTimeMillis()));
                return true;
            }

            //如果存在过，看下时间
            Long visitTime = Long.valueOf(visitTimeString);
            if (System.currentTimeMillis() - visitTime < expireTime) {
                logger.info("time error, duplication request");
                return false;
            }

            //超过有效的时间范围，写入标记
            JRedisUtil.setKV(key, String.valueOf(System.currentTimeMillis()));
            return true;
        }
    }

}
